package tech.runchen.gz5055.oms.common.utils;

import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.alibaba.fastjson.JSONObject;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class RSA2Utils {

    private final static Integer MAX_ENCRYPT_BLOCK = 245;
    private final static Integer MAX_DECRYPT_BLOCK = 256;
    private final static String SIGN_TYPE = "RSA";
    private final static Integer KEY_SIZE = 2048;

    /**
     * 生成RSA2公私钥
     *
     * @return
     */
    public static Map<String, String> generateKeyPair() {
        Map<String, String> pubPriKey = new HashMap<>();
        KeyPair keyPair = KeyUtil.generateKeyPair(SIGN_TYPE, KEY_SIZE);
        String publicKeyStr = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String privateKeyStr = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        pubPriKey.put("publicKey", publicKeyStr);
        pubPriKey.put("privateKey", privateKeyStr);
        return pubPriKey;
    }

    /**
     * 获取公钥
     *
     * @param publicKey
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(String publicKey) throws Exception {
        byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(StandardCharsets.UTF_8));
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 获取私钥
     *
     * @param privateKey
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(String privateKey) throws Exception {
        byte[] decodedKey = Base64.getDecoder().decode(privateKey.getBytes(StandardCharsets.UTF_8));
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 加密
     *
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String publicKey) throws Exception {
        data = URLEncoder.encode(data, "UTF-8");
        Cipher cipher = Cipher.getInstance(SIGN_TYPE);
        PublicKey publicKeyP = getPublicKey(publicKey);
        cipher.init(Cipher.ENCRYPT_MODE, publicKeyP);

        int inputLen = data.getBytes().length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        while (inputLen - offset > 0) {
            if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();

        return Base64.getEncoder().encodeToString(encryptedData);
    }

    /**
     * 解密
     *
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String decrypt(String data, String privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(SIGN_TYPE);
            PrivateKey privateKeyP = getPrivateKey(privateKey);
            cipher.init(Cipher.DECRYPT_MODE, privateKeyP);

            byte[] dataBytes = Base64.getDecoder().decode(data);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offset = 0;
            byte[] cache;
            int i = 0;
            while (inputLen - offset > 0) {
                if (inputLen - offset > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
                }
                out.write(cache, 0, cache.length);
                i++;
                offset = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            String dText = new String(decryptedData, StandardCharsets.UTF_8);
            return URLDecoder.decode(dText, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 签名
     *
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String sign(String data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);

        PrivateKey key = keyFactory.generatePrivate(keySpec);

        Signature signature = Signature.getInstance(SignAlgorithm.SHA256withRSA.getValue());
        signature.initSign(key);
        signature.update(data.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 验签
     *
     * @param data
     * @param publicKey
     * @param sign
     * @return
     * @throws Exception
     */
    public static boolean verify(String data, String publicKey, String sign) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);

        PublicKey key = keyFactory.generatePublic(keySpec);

        Signature signature = Signature.getInstance(SignAlgorithm.SHA256withRSA.getValue());
        signature.initVerify(key);
        signature.update(data.getBytes());
        return signature.verify(Base64.getDecoder().decode(sign));
    }


    public static void main(String[] args) {
        Map<String, String> result = generateKeyPair();
        System.out.println(JSONObject.toJSONString(result));

        String publicKey = result.get("publicKey");
        String privateKey = result.get("privateKey");

        String text = "这是一段加密测试文字";
        try {
            String eText = encrypt(text, publicKey);
            System.out.println("加密结果：" + eText);

            String sText = decrypt(eText, privateKey);
            System.out.println("解密结果：" + URLDecoder.decode(sText, "UTF-8"));
        } catch (Exception e) {
            System.out.println("报错啦");
        }

        try {
            String sign = sign(text, privateKey);
            System.out.println("签名结果：" + sign);
            sign += "test";
            boolean flag = verify(text, publicKey, sign);
            System.out.println("签名验证：" + flag);
        } catch (Exception e) {
            System.out.println("报错啦");
        }

    }

}
